import { AService, ServiceType } from "./AService";
import { ErrorCode } from "./ErrorCode";
import { Message, Request, Response } from "./IMessage";
import { IPEndPoint } from "./IPEndPoint";
import { MessageSerializeHelper } from "./MessageSerializeHelper";
import { OpcodeTypeManager } from "./OpcodeTypeManager";

export class Session extends et.Entity {
    public AService: AService;
    private static RpcId = 0;
    private readonly requestCallbacks = new Map<number, RpcInfo>();
    public LastRecvTime = 0;
    public LastSendTime = 0;
    public Error = 0;
    public awake(a: AService) {
        this.AService = a;
        let timenow = Date.now();
        this.LastRecvTime = timenow;
        this.LastSendTime = timenow;
        this.requestCallbacks.clear();
    }
    public dispose() {
        if (this.isDisposed) {
            return;
        }

        let zone = this.DomainZone();
        let id = this.id;

        super.dispose();

        this.AService.RemoveChannel(this.id);

        for (let responseCallback of this.requestCallbacks) {
            responseCallback[1].Reject(`Error: ${this.Error} Message: session dispose: ${id.toString()} ${this.RemoteAddress.toString()}`);
        }

        console.log(`session dispose: ${this.RemoteAddress.toString()} zone: ${zone} id: ${id} ErrorCode: ${this.Error}, please see ErrorCode.cs! ${Date.now()}`);

        this.requestCallbacks.clear();
    }
    public RemoteAddress: IPEndPoint;
    public OnRead(opcode: number, response: Response): void {
        //OpcodeHelper.LogMsg(this.DomainZone(), opcode, response);
        let action = this.requestCallbacks.get(response.RpcId);
        if (!action) {
            return;
        }

        this.requestCallbacks.delete(response.RpcId);
        if (ErrorCode.IsRpcNeedThrowException(response.Error)) {
            action.Reject(`Rpc error, request: ${action.Request} response: ${response}`);
            return;
        }
        action.Resolve(response);
    }
    public async Call<T extends Response>(request: Request)
    public async Call<T extends Response>(request: Request, cancellationToken: et.CancelToken = null) {
        let rpcId = ++Session.RpcId;
        let rpcInfo = new RpcInfo(request);
        this.requestCallbacks.set(rpcId, rpcInfo);
        request.RpcId = rpcId;

        this.Send1(request);
        let self = this;
        function CancelAction(): void {
            let action = self.requestCallbacks.get(rpcId);
            if (!action) {
                return;
            }
            let requestType = Object.getPrototypeOf(action.Request);
            self.requestCallbacks.delete(rpcId);
            let responseType = OpcodeTypeManager.Instance.GetResponseType(requestType.constructor);
            let response = new responseType();
            response.Error = ErrorCode.ERR_Cancel;
            action.Resolve(response);
        }

        let ret: Response;
        if (cancellationToken) {
            ret = await rpcInfo.Promise;
        }
        else {
            try {
                cancellationToken?.add(CancelAction, this);
                ret = await rpcInfo.Promise;
            }
            catch (e) {
                throw new Error(e);
            }
            finally {
                cancellationToken?.remove(CancelAction);
            }
        }
        return ret as T;
    }
    public Reply(message: Response): void {
        this.Send1(message);
    }
    public Send1(message: Message) {
        switch (this.AService.ServiceType) {
            case ServiceType.Inner:
                {
                    let arr = MessageSerializeHelper.MessageToStream2(Long.fromNumber(0), message);
                    //OpcodeHelper.LogMsg(this.DomainZone(), opcode, message);
                    this.Send3(Long.fromNumber(0), arr[1]);
                    break;
                }
            case ServiceType.Outer:
                {
                    let arr = MessageSerializeHelper.MessageToStream1(message);
                    //OpcodeHelper.LogMsg(this.DomainZone(), opcode, message);
                    this.Send3(Long.fromNumber(0), arr[1]);
                    break;
                }
        }
    }

    public Send2(actorId: Long, message: Message) {
        let arr = MessageSerializeHelper.MessageToStream2(actorId, message);
        //OpcodeHelper.LogMsg(this.DomainZone(), opcode, message);
        this.Send3(actorId, arr[1]);
    }

    public Send3(actorId: Long, memoryStream: DataView) {
        this.LastSendTime = Date.now();
        this.AService.SendStream(this.id, actorId, memoryStream);
    }
}
@et.objectSystem(Session)
class SessionAwakeSystem extends et.AwakeSystem1<Session, AService>{
    awake(self: Session, a: AService): void {
        self.awake(a);
    }
}
class RpcInfo {
    public Request: Request;
    public Promise: Promise<Response>;
    public Resolve: (request: Response) => void;
    public Reject: (request: string) => void;
    constructor(request: Request) {
        this.Request = request;
    }
}
